#!/usr/bin/env python
#
# Copyright (C) 2014 ABINIT Group (Yann Pouillon)
#
# This file is part of the ABINIT software package. For license information,
# please see the COPYING file in the top-level directory of the ABINIT source
# distribution.
#

from __future__ import print_function, division, absolute_import

from time import gmtime,strftime

try:
    from commands import getoutput
except ImportError:
    from subprocess import getoutput

try:
    from ConfigParser import ConfigParser,NoOptionError
except ImportError:
    from configparser import ConfigParser,NoOptionError

import os
import re
import sys

class MyConfigParser(ConfigParser):

    def optionxform(self,option):
        return str(option)

# ---------------------------------------------------------------------------- #

#
# Subroutines
#

# File template
def macros_triggers(name,stamp,m4_changed,m4_init,m4_summary):

    ret = """# Generated by %s on %s

#
# Display information about feature triggers status
#

#
# IMPORTANT NOTE
#
# This file has been automatically generated by the %s
# script. If you try to edit it, your changes will systematically be
# overwritten.
#



@M4_CHANGED@



@M4_INIT@



@M4_SUMMARY@
""" % (name,stamp,name)

    ret = re.sub("@M4_CHANGED@",m4_changed,ret)
    ret = re.sub("@M4_INIT@",m4_init,ret)
    ret = re.sub("@M4_SUMMARY@",m4_summary,ret)

    return ret



# Changed macro
def macro_changed(trig_params):

    ret = """\
# ABI_INFO_TRIGGERS_CHANGED()
# ----------------------
#
# Initializes parameters and command-line options related to feature triggers.
#
AC_DEFUN([ABI_INFO_TRIGGERS_CHANGED],[
@CHANGES@
]) # ABI_INFO_TRIGGERS_CHANGED"""

    trig_chgs = ""
    trig_list = sorted(trig_params.keys())

    for pkg in trig_list:

        opt_name = re.sub("_","-",pkg)
        if ( trig_params[pkg]["status"] == "new" ):
            trig_chgs += "  AC_MSG_NOTICE([  * new feature trigger for %s available])\n" % pkg
            trig_chgs += "  AC_MSG_NOTICE([    >>> --with-%s, %s_*FLAGS])\n" % (opt_name,pkg.upper())
            if ( "default_flavor" in trig_params[pkg] ):
                trig_chgs += "  AC_MSG_NOTICE([    >>> --with-%s-flavor (default: %s)])\n" % (pkg,trig_params[pkg]["default_flavor"])
        elif ( trig_params[pkg]["status"] == "removed" ):
            trig_chgs += "  AC_MSG_NOTICE([  * removed feature trigger for %s])\n" % pkg

    if ( trig_chgs == "" ):
        trig_chgs = "AC_MSG_NOTICE([no change in feature triggers to report])"
    else:
        trig_chgs = """\
  AC_MSG_NOTICE([])
  AC_MSG_NOTICE([reporting changes in feature triggers:])
  AC_MSG_NOTICE([])\n""" + trig_chgs

    ret = re.sub("@CHANGES@",trig_chgs,ret)

    return ret



# Init macro
def macro_init(trig_params):

    ret = """\
# ABI_TRIGGERS_INIT()
# -------------------
#
# Initializes parameters and command-line options related to feature triggers.
#
AC_DEFUN([ABI_TRIGGERS_INIT],[
@DEFINITIONS@
]) # ABI_TRIGGERS_INIT"""

    trig_template = """\
  # Init default parameters for @package@
  abi_@package@_enable="@default@"
  abi_@package@_init="def"
  abi_@package@_prefix=""

  # Command-line options for @package@
  AC_ARG_WITH([@package@],
    AC_HELP_STRING([--with-@package@],
      [Install prefix to look for @package@, or automatic configuration if set to 'yes' or 'no' (default: @default@)]),
    [ if test "${withval}" = "no" -o "${withval}" = "yes"; then
        abi_@package@_enable="${withval}"
        abi_@package@_init="yon"
        abi_@package@_prefix=""
      else
        abi_@package@_enable="yes"
        abi_@package@_init="dir"
        abi_@package@_cppflags="@includes@"
        abi_@package@_libs="@libraries@"
        abi_@package@_prefix="${withval}"
      fi])@with_flavor@
  AC_ARG_VAR([@PACKAGE@_CPPFLAGS],
      [C preprocessing flags for @package@ (default: empty)])
  AC_ARG_VAR([@PACKAGE@_FCFLAGS],
      [Fortran flags for @package@ (default: empty)])
  AC_ARG_VAR([@PACKAGE@_LIBS],
      [Library flags for @package@ (default: empty)])

  if test "${@PACKAGE@_FCFLAGS}${@PACKAGE@_LIBS}" != ""; then
    abi_@package@_enable="yes"
    abi_@package@_init="env"
  fi

  # Set up trigger-related variables for @package@
  abi_@package@_ok="unknown"
  if test "${abi_fbk_enable}" = "yes"; then
    abi_fbk_@package@_ok="@fbk_status@"
  else
    abi_fbk_@package@_ok="@nofbk_status@"
  fi@test_flavor@

  # Substitute trigger-related variables for @package@
  AC_SUBST(abi_@package@_enable)@subst_flavor@
  AC_SUBST(abi_@package@_init)
  AC_SUBST(abi_@package@_ok)
  AC_SUBST(abi_@package@_cppflags)
  AC_SUBST(abi_@package@_fcflags)
  AC_SUBST(abi_@package@_ldflags)
  AC_SUBST(abi_@package@_libs)
  AC_SUBST(abi_@package@_prefix)
  AC_SUBST(abi_fbk_@package@_ok)"""

    trig_sd_template = """\
  # Set up trigger-related variables for @package@
  if test "${abi_fbk_enable}" = "yes"; then
    abi_fbk_@package@_ok="@fbk_status@"
  else
    abi_fbk_@package@_ok="@nofbk_status@"
  fi

  # Substitute trigger-related variables for @package@
  AC_SUBST(abi_fbk_@package@_ok)"""

    trig_defs = ""
    trig_list = sorted(trig_params.keys())

    for pkg in trig_list:

        trig_substs= {
            "PACKAGE":pkg.upper(),
            "package":pkg,
            "name":trig_params[pkg]["name"],
        }
        if ( trig_params[pkg]["fallback"] == "yes" ):
            trig_substs["fbk_status"] = "avail"
            trig_substs["nofbk_status"] = "disabled"
        else:
            trig_substs["fbk_status"] = "no"
            trig_substs["nofbk_status"] = "no"
        if ( trig_params[pkg]["optional"] == "yes" ):
            trig_substs["default"] = "no"
        else:
            trig_substs["default"] = "yes"
        if ( "headers" in trig_params[pkg] or \
             "modules" in trig_params[pkg] ):
            trig_substs["includes"] = "-I${withval}/include"
        else:
            trig_substs["includes"] = ""
        if ( "libraries" in trig_params[pkg] ):
            trig_substs["libraries"] = \
            "-L${withval}/lib -l" + " -l".join(trig_params[pkg]["libraries"])
        else:
            trig_substs["libraries"] = ""
        if ( "flavors" in trig_params[pkg] ):
            flavors_list = [re.sub("^[+a-z0-9_]*:","",item) \
                for item in trig_params[pkg]["flavors"]]
            trig_substs["flavors"] = " ".join(flavors_list)
            trig_substs["default_flavor"] = trig_params[pkg]["flavors"][0]
            trig_substs["with_flavor"] = """
  AC_ARG_WITH([@package@-flavor],
    AC_HELP_STRING([--with-@package@-flavor],
      [Flavor of @name@ to use (default: @default_flavor@),
       see ~abinit/doc/build/config-template.ac9 for details]),
    [abi_@package@_flavor="${withval}"],
    [abi_@package@_flavor="@default_flavor@"])"""
            trig_substs["subst_flavor"] = "\n  AC_SUBST(abi_@package@_flavor)"
            trig_substs["test_flavor"] = """

  # Check that a permitted flavor value has been specified
  if test "${abi_@package@_enable}" = "yes"; then
    flavor_values=`echo "${abi_@package@_flavor}" | sed -e 's/+/ /g'`
    tmp_flavor_ok="no"
    AC_MSG_CHECKING([whether the '${abi_@package@_flavor}' @name@ flavor is valid])
    for chk_flavor in @flavors@; do
      for set_flavor in ${flavor_values}; do
        if test "${set_flavor}" = "${chk_flavor}"; then
          tmp_flavor_ok="yes"
          break
        fi
      done
      test "${tmp_flavor_ok}" = "yes" && break
    done
    AC_MSG_RESULT([${tmp_flavor_ok}])
    if test "${tmp_flavor_ok}" = "no"; then
      AC_MSG_ERROR([invalid @name@ flavor: '${abi_@package@_flavor}'])
    fi
  else
    abi_@package@_flavor="none"
  fi"""
        else:
            trig_substs["flavors"] = ""
            trig_substs["default_flavor"] = ""
            trig_substs["with_flavor"] = ""
            trig_substs["subst_flavor"] = ""
            trig_substs["test_flavor"] = ""

        # Make 2 passes of substitutions, since some substitutions contain
        # patterns themselves
        if ( trig_params[pkg]["detector"] == "steredeg" ):
            new_def = trig_sd_template
        else:
            new_def = trig_template
        for subst in trig_substs:
            new_def = re.sub("@%s@" % subst,trig_substs[subst],new_def)
        for subst in trig_substs:
            new_def = re.sub("@%s@" % subst,trig_substs[subst],new_def)
        if ( trig_defs != "" ):
            trig_defs += "\n\n"
        trig_defs += new_def

    ret = re.sub("@DEFINITIONS@",trig_defs,ret)

    return ret



# Summary macro
def macro_summary(trig_params):

    ret = """\
# ABI_TRIGGERS_SUMMARY()
# ----------------------
#
# Reports about the current feature triggers status.
#
AC_DEFUN([ABI_TRIGGERS_SUMMARY],[
@TRIG_FORMAT@
  # Display configuration
  cat <<EOF

Abinit feature triggers status
==============================

The following summary table indicates the status of the feature triggers,
whether they are available and working, how they have been initialized
and whether there are fallbacks to substitute broken ones.

@TRIG_STATUS@

EOF
]) # ABI_TRIGGERS_SUMMARY"""

    # Report status of each package
    trig_form  = ""
    trig_stat  = "  +----------------+--------+--------+--------+--------+\n"
    trig_stat += "  |Feature         |Enabled |Init    |Working |Fallback|\n"
    trig_stat += "  +----------------+--------+--------+--------+--------+\n"
    for pkg in sorted(trig_params.keys()):
        if ( trig_params[pkg]["detector"] == "steredeg" ):
            pfx = "sd"
        else:
            pfx = "abi"
        trig_form += "  tmp_e_%s=`echo \"${%s_%s_enable}\" | awk '{printf(\"%%-8s\",[$]1);}'`\n" % (pkg,pfx,pkg)
        trig_form += "  tmp_i_%s=`echo \"${%s_%s_init}\" | awk '{printf(\"%%-8s\",[$]1);}'`\n" % (pkg,pfx,pkg)
        trig_form += "  tmp_w_%s=`echo \"${%s_%s_ok}\" | awk '{printf(\"%%-8s\",[$]1);}'`\n" % (pkg,pfx,pkg)
        trig_form += "  tmp_f_%s=`echo \"${abi_fbk_%s_ok}\" | awk '{printf(\"%%-8s\",[$]1);}'`\n" % (pkg,pkg)
        trig_stat += "  |%-16s|${tmp_e_%s}|${tmp_i_%s}|${tmp_w_%s}|${tmp_f_%s}|\n" % \
            (pkg,pkg,pkg,pkg,pkg)
    trig_stat += "  +----------------+--------+--------+--------+--------+"

    # Substitute macro
    ret = re.sub("@TRIG_FORMAT@",trig_form,ret)
    ret = re.sub("@TRIG_STATUS@",trig_stat,ret)

    return ret



# ---------------------------------------------------------------------------- #

#
# Main program
#

# Initial setup
my_name    = "make-macros-triggers"
my_configs = ["config/specs/dependencies.conf"]
my_output  = "config/m4/auto-triggers.m4"

# Check if we are in the top of the ABINIT source tree
if ( not os.path.exists("configure.ac") or
     not os.path.exists("src/98_main/abinit.F90") ):
    print("%s: You must be in the top of an ABINIT source tree." % my_name)
    print("%s: Aborting now." % my_name)
    sys.exit(1)

# Read config file(s)
for cnf_file in my_configs:
    if ( os.path.exists(cnf_file) ):
        if ( re.search("\.cf$",cnf_file) ):
            execfile(cnf_file)
    else:
        print("%s: Could not find config file (%s)." % (my_name,cnf_file))
        print("%s: Aborting now." % my_name)
        sys.exit(2)

# What time is it?
now = strftime("%Y/%m/%d %H:%M:%S +0000",gmtime())

# Init
cnf = MyConfigParser()
cnf.read(my_configs[0])
abinit_triggers = []
abinit_fallbacks = []
triggers_params = {}
for pkg in cnf.sections():
    triggers_params[pkg] = {}
    triggers_params[pkg]["compounds"] = cnf.get(pkg,"compounds").split()
    triggers_params[pkg]["detector"] = cnf.get(pkg,"detector")
    triggers_params[pkg]["fallback"] = cnf.get(pkg,"fallback")
    triggers_params[pkg]["name"] = cnf.get(pkg,"name")
    triggers_params[pkg]["optional"] = cnf.get(pkg,"optional")
    triggers_params[pkg]["status"] = cnf.get(pkg,"status")
    abinit_triggers.append(pkg)
    if ( cnf.get(pkg,"fallback") == "yes" ):
        abinit_fallbacks.append(pkg)
    if ( cnf.has_option(pkg,"flavors") ):
        triggers_params[pkg]["flavors"] = cnf.get(pkg,"flavors").split()
abinit_triggers.sort()
abinit_fallbacks.sort()

# Write down macro
m4_changed = macro_changed(triggers_params)
m4_init = macro_init(triggers_params)
m4_summary = macro_summary(triggers_params)
with open(my_output, "w") as m4_file:
    m4_file.write(macros_triggers(my_name,now,m4_changed,m4_init,m4_summary))
